@page "/authentication"
@using UAParser
@inherits ComputedStateComponent<(User? User, List<AuthenticationPage.SessionInfoModel>? Sessions)>
@inject Session Session
@inject IAuth Auth
@inject ClientAuthHelper ClientAuthHelper
@inject IFusionTime FusionTime
@inject NavigationManager Nav

@{
    var state = State.LastNonErrorValue;
    var error = State.Error;
    var user = state.User.OrGuest();
    var sessions = state.Sessions ?? new();
    var usedAuthSchemas = user.Identities.Select(kv => kv.Key.Schema).ToHashSet();
    var unusedAuthSchemas = AuthSchemas.Where(p => !usedAuthSchemas.Contains(p.Name)).ToArray();
}

<h1>Authentication</h1>

<WhenException Title="Update error:" Exception="@error" />

<Note>
    <Div Margin="Margin.Is1.FromBottom">
        Open <a href="@Nav.Uri" target="_blank">this page</a> or
        <a href="/composition" target="_blank">Composition example</a>
        in another window to see how authentication state is synchronized everywhere.
    </Div>
    <Div>
        If authentication doesn't work, most likely you need to provide
        <code>ClientId</code> and <code>ClientSecret</code> for one of
        authentication providers.
    </Div>
</Note>

<Card>
    <CardBody>
        <CardTitle>Session properties:</CardTitle>
        <CardText>
            <ul>
                <li><code>Session.Id</code>: <b><code>@Session.Id</code></b></li>
            </ul>
        </CardText>

        <CardTitle>User properties:</CardTitle>
        <CardText>
            <ul>
                <li><code>User.Id</code>: <b><code>@user.Id</code></b></li>
                <li><code>User.Name</code>: <b><code>@user.Name</code></b></li>
                <li><code>User.Version</code>: <b><code>@user.Version</code></b></li>
            </ul>
            <div class="card-subtitle">Claims:</div>
            <ul>
                @foreach (var (key, value) in user.Claims) {
                    <li><code>@key</code>: <b><code>@value</code></b></li>
                }
            </ul>
            <div class="card-subtitle">Identities:</div>
            <ul>
                @foreach (var ((schema, schemaBoundId), _) in user.Identities) {
                    <li><code>@schema</code>: <b><code>@schemaBoundId</code></b></li>
                }
            </ul>
        </CardText>

        @if (sessions.Count > 0) {
            <CardTitle>Sessions:</CardTitle>
            <CardText>
                <table class="table table-sm">
                    <thead>
                    <tr>
                        <th scope="col">Session Hash</th>
                        <th scope="col">IP</th>
                        <th scope="col">Browser</th>
                        <th scope="col">Created</th>
                        <th scope="col">Last Seen</th>
                        <th scope="col">Version</th>
                        <th scope="col"></th>
                    </tr>
                    </thead>
                    <tbody>
                    @foreach (var session in sessions) {
                        <tr @key="@session.SessionHash">
                            <td>@session.SessionHash</td>
                            <td>@session.IPAddress</td>
                            <td>@session.UserAgent</td>
                            <td>@session.Created</td>
                            <td>@session.LastSeen</td>
                            <td>@session.Version</td>
                            <td>
                                @if (session.IsCurrent) {
                                    <span>Current</span>
                                } else {
                                    <button type="button" class="btn btn-sm btn-danger"
                                            @onclick="_ => ClientAuthHelper.Kick(Session, session.SessionHash)">Kick</button>
                                }
                            </td>
                        </tr>
                    }
                    </tbody>
                </table>
            </CardText>
        }
    </CardBody>
    <CardFooter>
        <AuthorizeView>
            <Authorized>
                <Buttons Margin="Margin.Is0">
                    <Button Color="Color.Warning"
                            @onclick="_ => ClientAuthHelper.SignOut()">Sign out</Button>
                    <Button Color="Color.Danger"
                            @onclick="_ => ClientAuthHelper.SignOutEverywhere()">Sign out everywhere</Button>
                </Buttons>
                @if (unusedAuthSchemas.Length != 0) {
                    <span> or add account: </span>
                    <Buttons Margin="Margin.Is0">
                        @foreach (var (name, displayName) in unusedAuthSchemas) {
                            <Button Color="Color.Primary"
                                    @onclick="_ => ClientAuthHelper.SignIn(name)">@displayName</Button>
                        }
                    </Buttons>
                }
            </Authorized>
            <NotAuthorized>
                <SignInDropdown/>
            </NotAuthorized>
        </AuthorizeView>
    </CardFooter>
</Card>

@code {
    public class SessionInfoModel
    {
        public string SessionHash { get; set; } = "";
        public string IPAddress { get; set; } = "";
        public string UserAgent { get; set; } = "";
        public string Created { get; set; } = "";
        public string LastSeen { get; set; } = "";
        public long Version { get; set; }
        public bool IsCurrent { get; set; }
    }

    private static readonly Parser Parser = Parser.GetDefault();

    private ConcurrentDictionary<string, UserAgent> UserAgentCache { get; } = new();
    private (string Name, string DisplayName)[] AuthSchemas { get; set; } = Array.Empty<(string, string)>();

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender) {
            // GetSchemas requires JS interop, so it can be called only at this point
            AuthSchemas = await ClientAuthHelper.GetSchemas();
            StateHasChanged();
        }
    }

    protected override async Task<(User?, List<SessionInfoModel>?)> ComputeState(CancellationToken cancellationToken)
    {
        var user = await Auth.GetUser(Session, cancellationToken);
        var sessionInfos = await Auth.GetUserSessions(Session, cancellationToken).ConfigureAwait(false);
        var result = new List<SessionInfoModel>();

        foreach (var sessionInfo in sessionInfos) {
            var userAgent = UserAgentCache.GetOrAdd(sessionInfo.UserAgent, static ua => Parser.ParseUserAgent(ua));
            var model = new SessionInfoModel() {
                SessionHash = sessionInfo.SessionHash,
                IsCurrent = sessionInfo.SessionHash == Session.Hash,
                IPAddress = sessionInfo.IPAddress,
                UserAgent = $"{userAgent.Family} {userAgent.Major}.{userAgent.Minor}",
                Created = await FusionTime.GetMomentsAgo(sessionInfo.CreatedAt),
                LastSeen = await FusionTime.GetMomentsAgo(sessionInfo.LastSeenAt),
                Version = sessionInfo.Version,
            };
            result.Add(model);
        }
        return (user, result);
    }
}
